/// @file Configuration.cpp
/// @brief static class, read the xml config file and initialises all the
/// the objects and managers used in the flocking system

#include <iostream>
#include "QString"

#include "pugixml.hpp"

#include "boost/tokenizer.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>

#include "ngl/Colour.h"
#include "ngl/VBOPrimitives.h"

#include "Configuration.h"


//static variables initialisation
std::string Configuration::s_settingsDoc = "config/Settings.xml";
bool Configuration::s_debug = true;
ngl::Real Configuration::s_accelerationOfFreeFall = -9.28;
int Configuration::s_nextParticleId = 0;
int Configuration::s_nextObstacleId = -1;

ngl::Colour Configuration::s_normalColour(0, 0, 1);
ngl::Colour Configuration::s_boundaryColour(1, 1, 1);

std::vector<ngl::Real> Configuration::getFloatVectorFromString
                                            (
                                                const std::string _str,
                                                const int _d
                                            )
{
    //split string into array of strings by ","
    std::vector<std::string> strArray;
    boost::split(strArray, _str, boost::is_any_of(","));

    //loop through strings and convert to float array
    std::vector<ngl::Real> floatArray;
    for (int i = 0; i < _d; i++) floatArray.push_back(boost::lexical_cast<ngl::Real>(strArray[i]));

    //return array of float
    return floatArray;
}

void Configuration::initialiseWindow
                                (
                                    int& o_frameRate,
                                    int& o_mouseMoveSensitivity,
                                    ngl::Real& o_timestep,
                                    bool& o_fpsTimerEnabled,
                                    bool& o_cacheEnabled,
                                    int& o_cacheSamplingInterval,
                                    bool& o_cacheAutomaticFlushEnabled,
                                    int& o_cacheAutomaticFlushInterval,
                                    bool& o_cacheExportRBDEnabled,
                                    bool& o_cacheExportBoundaryEnabled,
                                    std::string& o_path
                                )
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //load global settings here
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Global");

    o_frameRate = node.first().node().attribute("fpsFrequency").as_int();
    o_mouseMoveSensitivity = node.first().node().attribute("mouseSensitivity").as_int();
    o_timestep = node.first().node().attribute("timestep").as_float();
    o_fpsTimerEnabled = node.first().node().attribute("fpsTimerEnabled").as_bool();

    //read cache parameters
    node = doc.select_nodes("/Settings/Cache");

    o_cacheEnabled = node.first().node().attribute("enabled").as_bool();
    o_cacheSamplingInterval = node.first().node().attribute("samplingInterval").as_int();
    o_cacheAutomaticFlushEnabled = node.first().node().attribute("automaticFlush").as_bool();
    o_cacheAutomaticFlushInterval = node.first().node().attribute("automaticFlushInterval").as_int();
    o_cacheExportRBDEnabled = node.first().node().attribute("exportRBD").as_bool();
    o_cacheExportBoundaryEnabled = node.first().node().attribute("exportBoundary").as_bool();

    o_path = node.first().node().attribute("path").value();

}

ngl::Camera Configuration::initialiseCamera(const float _aspect)
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //get camera node
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Camera");

    //get near and far plane values
    float near = node.first().node().attribute("near").as_float();
    float far = node.first().node().attribute("far").as_float();

    //get fov
    int fov = node.first().node().attribute("fov").as_int();

    //get from and to values and convert to float arrays
    std::vector<float> from = getFloatVectorFromString(node.first().node().attribute("from").value(), 3);
    std::vector<float> to = getFloatVectorFromString(node.first().node().attribute("to").value(), 3);

    //build vectors
    ngl::Vector eye(from[0], from[1], from[2]);
    ngl::Vector look(to[0], to[1], to[2]);
    ngl::Vector up(0, 1, 0);

    //create a camera with the read data
    ngl::Camera cam;
    cam.set(eye, look, up);
    cam.setShape(fov, _aspect, near, far, ngl::PERSPECTIVE);

    return cam;
}

ngl::Light Configuration::initialiseLight()
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //get camera node
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Light");

    //get to values and convert to float array
    std::vector<float> to = getFloatVectorFromString(node.first().node().attribute("to").value(), 3);

    //get color values and convert to float array
    std::vector<float> color = getFloatVectorFromString(node.first().node().attribute("color").value(), 4);

    //build vectors
    ngl::Vector direction(to[0], to[1], to[2]);
    ngl::Colour lightColor(color[0], color[1], color[2], color[3]);

    //create a light with the read data
    ngl::Light light(direction, lightColor, ngl::LIGHTREMOTE);

    return light;
}

std::vector<ShaderObject*>* Configuration::initialiseShader()
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    std::vector<ShaderObject*>* result = new std::vector<ShaderObject*>;

    //get list of normal shaders
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Shaders/Shader");
    for (pugi::xpath_node_set::const_iterator it = node.begin(); it != node.end(); ++it)
    {
        //get details for a shader
        pugi::xpath_node node = *it;

        //save values in array
        std::string name = node.node().attribute("name").value();
        std::string vs = node.node().attribute("vertex").value();
        std::string fs = node.node().attribute("fragment").value();
        bool isSpecial = node.node().attribute("isSpecial").as_bool();

        std::vector<std::string> attribNames;
        std::vector<int> attribValues;

        if (isSpecial)
        {
            //load attributes
            pugi::xpath_node_set attributes = node.node().select_nodes("Attrib");
            for (pugi::xpath_node_set::const_iterator it1 = attributes.begin(); it1 != attributes.end(); ++it1)
            {
                pugi::xpath_node innerNode = *it1;

                std::string attribName = innerNode.node().attribute("name").value();
                int value = innerNode.node().attribute("value").as_int();

                attribNames.push_back(attribName);
                attribValues.push_back(value);
            }
        }

        //create a shader and add it to our list
        result->push_back(new ShaderObject(name, vs, fs, attribNames, attribValues, isSpecial));
    }

    return result;
}

void Configuration::initialiseEnvironment
                                (
                                    bool& o_obstacleEnabled,
                                    ngl::Vector& o_boundaryPosition,
                                    ngl::Vector& o_boundaryDimension,
                                    ngl::Real& o_boundaryRestitutionCoefficientForFluid,
                                    ngl::Real& o_boundaryRestitutionCoefficientForRBD,
                                    bool& o_boundaryBoundTop,
                                    std::vector<Particle>& o_sphereObstacleList,
                                    std::vector<Capsule>& o_capsuleObstacleList,
                                    ngl::Real& o_obstacleRestitutionCoefficient,
                                    bool& o_periodicWallEnabled,
                                    ngl::Real& o_periodicWallMaxAmplitude,
                                    ngl::Real& o_periodicWallSpeed,
                                    ngl::Real& o_periodicWallAngleIncrement,
                                    int& o_capsuleResolution
                                )
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //temporary variable
    std::vector<ngl::Real> tmp;

    //load global settings here
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Environment");

    //read info about the boundary
    tmp = getFloatVectorFromString(node.first().node().select_single_node("Boundary").node().attribute("position").value(), 3);
    o_boundaryPosition.set(tmp[0], tmp[1], tmp[2]);

    tmp = getFloatVectorFromString(node.first().node().select_single_node("Boundary").node().attribute("dimension").value(), 3);
    o_boundaryDimension.set(tmp[0], tmp[1], tmp[2]);

    o_boundaryRestitutionCoefficientForFluid = node.first().node().select_single_node("Boundary").node().attribute("restitutionForFluid").as_float();
    o_boundaryRestitutionCoefficientForRBD = node.first().node().select_single_node("Boundary").node().attribute("restitutionForRBD").as_float();

    o_boundaryBoundTop = node.first().node().select_single_node("Boundary").node().attribute("boundTop").as_bool();

    //read info about periodic wall
    o_periodicWallEnabled = node.first().node().select_single_node("Boundary").node().attribute("periodicWallEnabled").as_bool();

    node = node.first().node().select_single_node("Boundary").node().select_nodes("PeriodicWall");

    o_periodicWallMaxAmplitude = node.first().node().attribute("maxAmplitude").as_float();
    o_periodicWallSpeed = node.first().node().attribute("speed").as_float();
    o_periodicWallAngleIncrement = node.first().node().attribute("angleIncrement").as_float();

    //read info about obstacles
    node = doc.select_nodes("/Settings/Environment/Obstacles");
    o_obstacleEnabled =  node.first().node().attribute("enabled").as_bool();

    o_obstacleRestitutionCoefficient = node.first().node().attribute("restitution").as_float();

    o_capsuleResolution = node.first().node().attribute("capsuleResolution").as_int();

    //get list of sphere obstacles
    node = node.first().node().select_nodes("Sphere");
    for (pugi::xpath_node_set::const_iterator it = node.begin(); it != node.end(); ++it)
    {
        //get details for an obstacle
        pugi::xpath_node node1 = *it;

        //get parameters active parameter
        tmp = getFloatVectorFromString(node1.node().attribute("position").value(), 3);
        ngl::Vector position(tmp[0], tmp[1], tmp[2]);

        tmp = getFloatVectorFromString(node1.node().attribute("velocity").value(), 3);
        ngl::Vector velocity(tmp[0], tmp[1], tmp[2]);

        ngl::Real radius = node1.node().attribute("radius").as_float();

        ngl::Real moveable = node1.node().attribute("moveable").as_bool();

        tmp = getFloatVectorFromString(node1.node().attribute("colour").value(), 3);
        ngl::Colour colour(tmp[0], tmp[1], tmp[2]);

        //create and add obstacle to list
        o_sphereObstacleList.push_back
                (
                    Particle
                    (
                        Configuration::s_nextObstacleId--,
                        0,
                        position,
                        colour,
                        radius,
                        moveable,
                        velocity
                    )
                );
    }

    //get list of capsule obstacles
    node = doc.select_nodes("/Settings/Environment/Obstacles/Capsule");
    for (pugi::xpath_node_set::const_iterator it = node.begin(); it != node.end(); ++it)
    {
        //get details for an obstacle
        pugi::xpath_node node1 = *it;

        //get parameters active parameter
        tmp = getFloatVectorFromString(node1.node().attribute("position1").value(), 3);
        ngl::Vector position1(tmp[0], tmp[1], tmp[2]);

        tmp = getFloatVectorFromString(node1.node().attribute("position2").value(), 3);
        ngl::Vector position2(tmp[0], tmp[1], tmp[2]);

        ngl::Real incrementAngle = node1.node().attribute("incrementAngle").as_float();

        ngl::Real initialAngle = node1.node().attribute("initialAngle").as_float();

        ngl::Real radius = node1.node().attribute("radius").as_float();

        ngl::Real moveable = node1.node().attribute("moveable").as_bool();

        tmp = getFloatVectorFromString(node1.node().attribute("colour").value(), 3);
        ngl::Colour colour(tmp[0], tmp[1], tmp[2]);

        //calculate orientation vector
        ngl::Vector orientationVector = position2 - position1;

        //calculate height half
        ngl::Real height = orientationVector.length() / 2.0;

        //normalise orientation vector
        orientationVector.normalize();

        //calculate centre position
        ngl::Vector centrePosition = position1 + (orientationVector * height);

        std::cout << "test Capsules : " << centrePosition << "\n";

        //create and add obstacle to list
        o_capsuleObstacleList.push_back
                (
                    Capsule
                    (
                        Configuration::s_nextObstacleId--,
                        0,
                        centrePosition,
                        colour,
                        radius,
                        height,
                        orientationVector,
                        incrementAngle,
                        initialAngle,
                        moveable
                    )
                );
    }

}

void Configuration::initialiseFluidSolver
                                (
                                    ngl::Real& o_smoothingLength,
                                    std::vector<FluidParticle>& o_particleList,
                                    std::vector<FluidParticle>& o_hoseParticlePrototypeList,
                                    std::vector<FluidParticle>& o_hoseParticleList,
                                    ngl::Vector& o_centerOfHose,
                                    ngl::Vector& o_velocityOfHose,
                                    bool& o_drawHoseMarker,
                                    bool& o_hoseWaitUntilFirstHitBoundary,
                                    bool& o_hoseWaitUntilFirstHitRBD
                                )
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //load global settings here
    pugi::xpath_node_set node = doc.select_nodes("/Settings/FluidSolver");

    //read smoothing length
    o_smoothingLength = node.first().node().attribute("smoothingLength").as_float();

    //initially reserve space for maximum growth of particle list
    int initialReservedParticleCount =node.first().node().attribute("initialReservedParticleCount").as_int();
    o_particleList.reserve(initialReservedParticleCount);

    //tmp variable
    std::vector<ngl::Real> tmp;

    //get hose info
    node = doc.select_nodes("/Settings/FluidSolver/Hose");

    tmp = getFloatVectorFromString(node.first().node().attribute("center").value(), 3);
    o_centerOfHose = ngl::Vector(tmp[0], tmp[1], tmp[2]);

    o_hoseWaitUntilFirstHitBoundary = node.first().node().attribute("waitUntilHitBoundary").as_bool();
    o_hoseWaitUntilFirstHitRBD = node.first().node().attribute("waitUntilHitRBD").as_bool();

    o_drawHoseMarker = node.first().node().attribute("drawMarker").as_bool();

    std::string hoseMeshName = node.first().node().select_single_node("Obj").node().child_value();
    ngl::Obj hoseMesh(hoseMeshName);

    tmp = getFloatVectorFromString(node.first().node().select_single_node("Obj").node().attribute("center").value(), 3);
    ngl::Vector centerOfHoseMesh(tmp[0], tmp[1], tmp[2]);

    tmp = getFloatVectorFromString(node.first().node().select_single_node("Velocity").node().attribute("value").value(), 3);
    o_velocityOfHose = ngl::Vector(tmp[0], tmp[1], tmp[2]);

    for (int i = 0; i < hoseMesh.getNumVerts(); i++)
    {
        //create particle @ mesh vertices and add to hose particle list
        o_hoseParticleList.push_back
                (
                        FluidParticle
                        (
                                0,
                                0,
                                0,
                                centerOfHoseMesh + hoseMesh.getVertexAtIndex(i)
                        )
                );
    }

    //get a list of fluids
    node = doc.select_nodes("/Settings/FluidSolver/Fluid");
    for (pugi::xpath_node_set::const_iterator it = node.begin(); it != node.end(); ++it)
    {
        //get details for a fluid
        pugi::xpath_node node = *it;

        //get its active parameter
        bool isActive = node.node().attribute("active").as_bool();

        //skip this fluid if not active
        if (isActive)
        {
            //else, active fluid => go on creating and adding particles of fluid to particle list

            //read parameters
            ngl::Real volume = boost::lexical_cast<ngl::Real>(node.node().select_single_node("Volume").node().child_value());

            ngl::Real materialDensity = boost::lexical_cast<ngl::Real>(node.node().select_single_node("MaterialDensity").node().child_value());

            tmp = getFloatVectorFromString(node.node().select_single_node("Colour").node().attribute("value").value(), 3);
            ngl::Colour color(tmp[0], tmp[1], tmp[2]);

            std::string meshName = node.node().select_single_node("Obj").node().child_value();
            ngl::Obj mesh(meshName);

            tmp = getFloatVectorFromString(node.node().select_single_node("Obj").node().attribute("center").value(), 3);
            ngl::Vector centerOfMesh(tmp[0], tmp[1], tmp[2]);

            tmp = getFloatVectorFromString(node.node().select_single_node("Velocity").node().attribute("value").value(), 3);
            ngl::Vector velocity(tmp[0], tmp[1], tmp[2]);

            ngl::Real viscosityConstant = boost::lexical_cast<ngl::Real>(node.node().select_single_node("ViscosityConstant").node().child_value());

            ngl::Real gasConstant = boost::lexical_cast<ngl::Real>(node.node().select_single_node("GasConstant").node().child_value());

            ngl::Real surfaceTensionCoefficient = node.node().select_single_node("SurfaceTension").node().attribute("coefficient").as_float();
            ngl::Real surfaceTensionThreshold = node.node().select_single_node("SurfaceTension").node().attribute("threshold").as_float();
            ngl::Real surfaceColorCoefficient = node.node().select_single_node("SurfaceTension").node().attribute("color").as_float();

            ngl::Real interfaceTensionCoefficient = node.node().select_single_node("SurfaceInterface").node().attribute("coefficient").as_float();
            ngl::Real interfaceTensionThreshold = node.node().select_single_node("SurfaceInterface").node().attribute("threshold").as_float();
            ngl::Real interfaceColorCoefficient = node.node().select_single_node("SurfaceInterface").node().attribute("color").as_float();

            ngl::Real radius = boost::lexical_cast<ngl::Real>(node.node().select_single_node("Radius").node().child_value());

            std::string name = node.node().attribute("name").value();

            //calculate mass per particle
            int particleCount = mesh.getNumVerts();
            ngl::Real mass = materialDensity * (volume / ((ngl::Real)particleCount));

            std::cout << "Mesh : " << meshName << "\tparticle count : " << particleCount << "\tunit mass : " << mass << "\n";

            //create fluid particles from obj
            for (int i = 0; i < particleCount; i++)
            {
                //create particle @ mesh vertices and add to particle list
                o_particleList.push_back
                        (
                                FluidParticle
                                (
                                        Configuration::s_nextParticleId++,
                                        mass,
                                        materialDensity,
                                        centerOfMesh + mesh.getVertexAtIndex(i),
                                        velocity,
                                        viscosityConstant,
                                        gasConstant,
                                        surfaceTensionCoefficient,
                                        surfaceTensionThreshold,
                                        surfaceColorCoefficient,
                                        interfaceTensionCoefficient,
                                        interfaceTensionThreshold,
                                        interfaceColorCoefficient,
                                        color,
                                        radius,
                                        name
                                )
                        );
            }
            std::cout << "Fluid : << " << name << "\tListSize : " << o_particleList.size() << "\n";

            //add a sample of this fluid's particle to hose prototype list
            o_hoseParticlePrototypeList.push_back
                    (
                            FluidParticle
                            (
                                    -1,
                                    mass,
                                    materialDensity,
                                    0,
                                    0,
                                    viscosityConstant,
                                    gasConstant,
                                    surfaceTensionCoefficient,
                                    surfaceTensionThreshold,
                                    surfaceColorCoefficient,
                                    interfaceTensionCoefficient,
                                    interfaceTensionThreshold,
                                    interfaceColorCoefficient,
                                    color,
                                    radius,
                                    name,
                                    o_hoseWaitUntilFirstHitBoundary,
                                    o_hoseWaitUntilFirstHitRBD
                            )
                    );
        }
    }
}
